#include "requestMessages.h"

/**
 * Handles receiving a request message. Discards the request if it cannot locate
 * the requested file within it's local system and does not have a record of the
 * file within its content directory list. In the event it can serve the request
 * from its local system or data cache, it will send to the requester the file 
 * in a form of a data message, which will contain this p2p's ip address and port
 * number in the originator field. The data cache will update itself to reflect
 * the LRU algorithm, such that the most recent entry is at the tail or end of
 * the list and the least at the beginning or head of the list.
 * In the event, the p2p server can't locate the file within its local system or
 * from the data cache, but does have a record of it in its content directory list,
 * it will send a try message to the requester with a list of all peers that it
 * knows has the desired file.
 *
 * @param message The request message.
 * @param senderInfo Contains the ip address and port number of the requester.
 */
void handleRequestMessage(unsigned char * message, struct addressPort senderInfo) {
	
	if (debug) {
		struct node * iterator = createIterator(contentDirectoryList);
		while (hasNext(iterator)) {
			next(&iterator);			
			struct contentDir * cdt = iterator->payload;
			printf("handleMessage function: cdt file is %s\n", cdt->name);
		}
	}
	
	int isInContentDirectory = false;
	
	// To temporarly hold the data cache item if it is found there.
	struct dataCache * dataCacheItem;
	
	unsigned char filename[MAX_NAME];
	memset(filename, 0, sizeof(filename));
		
	// Will represent the current location in data message buffer and the overall message size.
	int messageIndex = 0;

	// Loop variable
	int i;

	// Determine the filename provided by the request message.
	strcpy((char *) filename, (char *) message+2);
		
	// Add 1 initially to account for the selector byte in the request message.
	messageIndex = 1 + strlen((char *) filename)+2;
	
	if (debug) {
		printf("handleRequestMessage function: The filename is %s\n", filename);
	}
		
	// The requested file is on p2p's local system, proceed to retreive and send to requester.
	if ((searchList(&listLocalFiles, filename, 0)) == true) {
		
		if (debug) {
			printf("handleRequestMessage function: %s found on local system\n", filename);
		}
		sendDataMessageForLocalContent(filename, senderInfo);
		
	}
	// The requested file is within the data cache. Proceed to retreive and send to requester.
	else if ((dataCacheItem = getDataCacheItem(filename)) != NULL) {
	
		if (debug) {
			printf("handleRequestMessage function: %s found within the data cache\n", filename);
		}
		sendDataMessageForDataCache(filename, senderInfo, dataCacheItem);
	
	}
	// Check to see if there are any peers with requested file in our content directory.
	else {		
		struct node * iterator = createIterator(contentDirectoryList);
		struct node * freeIterator = iterator;
		while (hasNext(iterator)) {
			next(&iterator);
			struct contentDir * cdt = iterator->payload;
			if ((strcmp(filename, cdt->name) == 0)) {
				isInContentDirectory = true;
				break;
			}
		}
	}			
		
	// The requested file is neither in the local content or data cache but some peers in the
	// content directory have the files. Send a try listing to requester with ip address and
	// port numbers of the peers that have the file.
	if (isInContentDirectory == true) {
		
		if (debug) {
			printf("handleRequestMessage function: %s found within the content directory\n", filename);	
		}
		sendTryMessage(filename, senderInfo);
	}
}

/**
 * Creates and sends to the originator of a request, a data message containing
 * the file that was requested of this p2p application. This is used for the
 * case the requested file is located within the local directory.
 *
 * @param filename The filename requested.
 * @param senderInfo Contains the sender's ip address and port number.
 */
void sendDataMessageForLocalContent(unsigned char * filename, struct addressPort senderInfo) {

	// The data message buffer.
	char dataMessage[MAX_MSG];
	int i;

	// Data messages are defined by 0xDD in its first index.
	dataMessage[0] = 0xDD;
	int messageIndex = 1;

	strcpy(dataMessage+messageIndex, filename);
	messageIndex += strlen((char *) filename)+1;
		
	if (debug) {
		printf("sendDataMessageForLocalContent function: The filename is %s\n", filename);
	}
		
	char * senderAddress = ipToString(senderInfo.address);
		
	if (debug) {
		printf("sendDataMessageForLocalContent function: The sender of the request message ip address is %s\n", senderAddress);
	}
		
	// Keep track of 2 indexes not filled in the message to be filled later. The file data size in bytes.
	int emptyIndexInMessage = messageIndex;
	printf("I'm about to be angry!");
	messageIndex += 2; 	
				
	unsigned short fileSize = readFile(dataMessage+messageIndex, filename);
	messageIndex = messageIndex + fileSize;
	
		
	// Can only transfer a file up to a application-defined size.
	if (fileSize > MAX_CONTENT) {
		perror("File exceeds transfer capacity\n");
		return;
	}
		
	// Will not serve empty files.
	if (fileSize < 1) {
		perror("File is empty, can't serve\n");
		return;
	}
		
	if (debug) {
		printf("sendDataMessageForLocalContent function: fileSize is %d\n", fileSize);
		printf("sendDataMessageForLocalContent function: file contains: ");
		for (i = 0; i < fileSize; i++) {
			printf("%c", dataMessage[messageIndex-fileSize+i]);
		}
		printf("\n");
	}
		
	// Fill in the 2 empty indexes within the data message with the 2-byte short
	// that denotes the content length.
	unsigned short * clen = (unsigned short *) &dataMessage[emptyIndexInMessage];
	*clen = htons(fileSize);
		
	if (debug) {
		printf("sendDataMessageForLocalContent function: message size is %d\n", messageIndex);
		printf("sendDataMessageForLocalContent function: message being sent ");
		printMessage(dataMessage, messageIndex);
	}
	
	// We have created our data message, now ready to send to the requester.
	sendUDP(senderAddress, senderInfo.port, socketfd, dataMessage, messageIndex);

}

/**
 * Creates and sends to the originator of a request, a data message containing
 * the file that was requested of this p2p application. This is used for the
 * case the requested file is located within the data cache.
 *
 * @param filename The filename requested.
 * @param senderInfo Contains the sender's ip address and port number.
 * @param dataCacheItem dataCache struct that contains the file content and related information.
 */
void sendDataMessageForDataCache(unsigned char * filename, struct addressPort senderInfo, struct dataCache * dataCacheItem) {

	// The data message buffer.
	char dataMessage[MAX_MSG];
	int i;

	// Data messages are defined by 0xDD in its first index.
	dataMessage[0] = 0xDD;
	int messageIndex = 1;

	strcpy(dataMessage+messageIndex, filename);
	messageIndex += strlen((char *) filename)+1;
		
	if (debug) {
		printf("sendDataMessageForDataCache function: The filename is %s\n", filename);
	}
		
	char * senderAddress = ipToString(senderInfo.address);
		
	if (debug) {
		printf("sendDataMessageForDataCache function: The sender of the request message ip address is %s\n", senderAddress);
	}

	// Retrieve the length of the file and convert from host to network short.
	int fileSize = dataCacheItem->contentLength;
	unsigned short * clen = (unsigned short *) &dataMessage[messageIndex];
	*clen = htons(fileSize);
	messageIndex += 2; 	
		
	// Can only transfer a file up to a application-defined size.
	if (fileSize > MAX_CONTENT) {
		perror("File exceeds transfer capacity\n");
		return;
	}
		
	// Will not serve empty files.
	if (fileSize < 1) {
		perror("File is empty, can't serve\n");
		return;
	}
		
	// Fill in the data message buffer with data cache content.
	for (i = 0; i < fileSize; i++) {
		dataMessage[messageIndex+i] = dataCacheItem->content[i];
	}
		
	messageIndex = messageIndex + fileSize;
	
	if (debug) {
		printf("sendDataMessageForDataCache function: message size is %d\n", messageIndex);
		printf("sendDataMessageForDataCache function: message being sent ");
		printMessage(dataMessage, messageIndex);
	}
		
	// We have created our data message, now ready to send to the requester.
	sendUDP(senderAddress, senderInfo.port, socketfd, dataMessage, messageIndex);
}

/**
 * Creates and sends to the originator of a request, a try message containing
 * the peers that have the requested file if it is not within the local content
 * or data cache of this p2p.
 *
 * @param filename The filename requested.
 * @param senderInfo Contains the sender's ip address and port number.
 */
void sendTryMessage(unsigned char * filename, struct addressPort senderInfo) {
	
	// The data message buffer.
	char tryMessage[MAX_MSG];
	int i;

	// Try messages are defined by 0xCC followed by 0x54 at the beginning of the message.
	tryMessage[0] = 0xCC;
	tryMessage[1] = 0x54;
	int messageIndex = 2;

	strcpy(tryMessage+messageIndex, filename);
	messageIndex += strlen((char *) filename)+1;
	
	if (debug) {
		printf("sendTryMessage function: The filename is %s\n", filename);
	}
	
	char * senderAddress = ipToString(senderInfo.address);

	if (debug) {
		printf("sendTryMessage function: The sender of the request message ip address is %s\n", senderAddress);
	}
	
	// Keep track of 2 indexes not filled in the message to be filled later. A short to represent peer count.
	int emptyIndexInMessage = messageIndex;
	messageIndex += 2; 	
	
	unsigned short peerCount = 0;
	
	struct node * iterator = createIterator(contentDirectoryList);
	struct node * previousNode;
	struct node * freeIterator = iterator;
	
	// Track the amount of times the iterator has move forward
	int iteration = 0; 
	// Search through the content directory and find peers who have the requested files
	while (iteration < contentDirectoryList.size || hasNext(iterator)) {
		
		previousNode = next(&iterator);
		struct contentDir * cd = (struct contentDir *) iterator->payload;
		
		// A match is found; retrieve ip address and port number information
		// and fill them into the try message buffer.
		if ((strcmp(filename, cd->name)) == 0) {
		
			unsigned long address = cd->address;
			unsigned long * ip = (unsigned long *) &tryMessage[messageIndex];
			*ip = htonl(address);
			messageIndex += 4;
			
			unsigned short port = cd->port;
			unsigned short * pn = (unsigned short *) &tryMessage[messageIndex];
			*pn = htons(port);
			messageIndex += 2;
			
			// Also send a request to a matched peer to retrieve the file for this p2p application
			// and store into data cache & local content.
			sendRequestMessage(filename, address, port);
			
			// Sort the matched content directory entry to reflect LRU by putting at the end of the list.
			if (contentDirectoryList.size == 1 || iterator == contentDirectoryList.tail) {
			  ;
			}
			// Handle for case such that the node to sort is the head node.			
			else if (iterator == contentDirectoryList.head){
					contentDirectoryList.head = iterator->next;
					contentDirectoryList.tail->next = iterator;
					contentDirectoryList.tail = iterator;
					contentDirectoryList.tail->next = NULL;
			}
			// Otherwise handle all other cases.
			else {
					previousNode->next = iterator->next;
					contentDirectoryList.tail->next = iterator;
					contentDirectoryList.tail = iterator;
					contentDirectoryList.tail->next = NULL;
			}					
			peerCount++;
		}
		iteration++;
	}
	
	// Fill in the 2 empty indexes within the try message with the 2-byte short
	// that denotes the peer count.
	unsigned short * peers = (unsigned short *) &tryMessage[emptyIndexInMessage];
	*peers = htons(peerCount);
	
	if (debug) {
		printf("sendTryMessage function: message size is %d\n", messageIndex);
		printf("sendTryMessage function: ");
		printMessage(tryMessage, messageIndex);
		printf("sendTryMessage function: Sending try message to requester\n");
	}
	
	sendUDP(senderAddress, senderInfo.port, socketfd, tryMessage, messageIndex);
	free(freeIterator);
	push(&outstandingRequestsList, createOutstandingReqItem(filename, 1));
}

/**
 * Creates and sends a request message given a filename, destination ip address, and 
 * destination port number.
 *
 * @param filename The name of file to be requested from peer.
 * @param ipAddress The destination peer ip address.
 * @param port The destination peer port number.
 */
void sendRequestMessage(unsigned char * filename, unsigned long ipAddress, unsigned short port) {
	
	char * destinationAddress = ipToString(ipAddress);
	
	// The data message buffer.
	char requestMessage[MAX_MSG];
	int i;

	// Request messages are defined by 0xCC followed by 0x54 at the beginning of the message.
	requestMessage[0] = 0xCC;
	requestMessage[1] = 0x52;
	int messageIndex = 2;

	strcpy(requestMessage+messageIndex, filename);
	messageIndex += strlen((char *) filename)+1;
	
	if (debug) {
		printf("sendRequestMessage function: message size is %d\n", messageIndex);
		printf("sendRequestMessage function: message being sent ");
		printMessage(requestMessage, messageIndex);
		printf("sendRequestMessage function: Sending request message to peer\n");
	}
	
	sendUDP(destinationAddress, port, socketfd, requestMessage, messageIndex);
}